﻿using lmsft.SIP.Message;
using System;
using System.Threading;

namespace lmsft.SIP.Stack
{
	/// <summary>
	/// Implements SIP client transaction. Defined in RFC 3261 17.1.
	/// </summary>
	public class SIP_ClientTransaction : SIP_Transaction
	{
		private TimerEx m_pTimerA = null;
		private TimerEx m_pTimerB = null;
		private TimerEx m_pTimerD = null;
		private TimerEx m_pTimerE = null;
		private TimerEx m_pTimerF = null;
		private TimerEx m_pTimerK = null;
		private TimerEx m_pTimerM = null;
		private bool m_IsCanceling = false;


	   
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="stack">Owner SIP stack.</param>
		/// <param name="flow">SIP data flow which is used to send request.</param>
		/// <param name="request">SIP request that transaction will handle.</param>
		/// <exception cref="ArgumentNullException">Is raised when <b>stack</b>,<b>flow</b> or <b>request</b> is null reference.</exception>
		/// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
		internal SIP_ClientTransaction(SIP_Stack stack, SIP_Flow flow, SIP_Request request) : base(stack, flow, request) {
			// Log
			if (this.Stack.Logger != null) {
				this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] created.");
			}

			SetState(SIP_TransactionState.WaitingToStart);
		}

		#region method Dispose

		/// <summary>
		/// Cleans up any resources being used.
		/// </summary>
		public override void Dispose() {
			lock (this.SyncRoot) {
				if (this.IsDisposed) {
					return;
				}

				// Log
				if (this.Stack.Logger != null) {
					this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] disposed.");
				}

				// Kill timers.
				if (m_pTimerA != null) {
					m_pTimerA.Dispose();
					m_pTimerA = null;
				}
				if (m_pTimerB != null) {
					m_pTimerB.Dispose();
					m_pTimerB = null;
				}
				if (m_pTimerD != null) {
					m_pTimerD.Dispose();
					m_pTimerD = null;
				}
				if (m_pTimerE != null) {
					m_pTimerE.Dispose();
					m_pTimerE = null;
				}
				if (m_pTimerF != null) {
					m_pTimerF.Dispose();
					m_pTimerF = null;
				}
				if (m_pTimerK != null) {
					m_pTimerK.Dispose();
					m_pTimerK = null;
				}
				if (m_pTimerM != null) {
					m_pTimerM.Dispose();
					m_pTimerM = null;
				}

				this.ResponseReceived = null;

				base.Dispose();
			}
		}

		#endregion


		#region Events handling

		#region method m_pTimerA_Elapsed

		/// <summary>
		/// Is raised when INVITE timer A triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerA_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			/* RFC 3261 17.1.1.2.
                When timer A fires, the client transaction MUST retransmit the
                request by passing it to the transport layer, and MUST reset the
                timer with a value of 2*T1.  The formal definition of retransmit
                within the context of the transaction layer is to take the message
                previously sent to the transport layer and pass it to the transport
                layer once more.
             
                When timer A fires 2*T1 seconds later, the request MUST be
                retransmitted again (assuming the client transaction is still in this
                state).  This process MUST continue so that the request is
                retransmitted with intervals that double after each transmission.
                These retransmissions SHOULD only be done while the client
                transaction is in the "calling" state.
            */

			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Calling) {
						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) triggered.");
						}

						try {
							// Retransmit request.
							this.Stack.TransportLayer.SendRequest(this.Flow, this.Request, this);
						}
						catch (Exception x) {
							OnTransportError(x);
							SetState(SIP_TransactionState.Terminated);
							return;
						}

						// Update(double current) next transmit time.
						m_pTimerA.Interval *= 2;
						m_pTimerA.Enabled = true;

						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) updated, will trigger after " + m_pTimerA.Interval + ".");
						}
					}
				}
				catch  {
				}
			}
		}

		#endregion

		#region method m_pTimerB_Elapsed

		/// <summary>
		/// Is raised when INVITE timer B triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerB_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			/* RFC 3261 17.1.1.2.
                If the client transaction is still in the "Calling" state when timer
                B fires, the client transaction SHOULD inform the TU that a timeout
                has occurred.  The client transaction MUST NOT generate an ACK.  The
                value of 64*T1 is equal to the amount of time required to send seven
                requests in the case of an unreliable transport.
            */

			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Calling) {
						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) triggered.");
						}

						OnTimedOut();
						SetState(SIP_TransactionState.Terminated);

						// Stop timers A,B.
						if (m_pTimerA != null) {
							m_pTimerA.Dispose();
							m_pTimerA = null;
						}
						if (m_pTimerB != null) {
							m_pTimerB.Dispose();
							m_pTimerB = null;
						}
					}
				}
				catch {
				}
			}
		}

		#endregion

		#region method m_pTimerD_Elapsed

		/// <summary>
		/// Is raised when INVITE timer D triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerD_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			/* RFC 3261 17.1.1.2.
                If timer D fires while the client transaction is in the "Completed"
                state, the client transaction MUST move to the terminated state.
            */

			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Completed) {
						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) triggered.");
						}

						SetState(SIP_TransactionState.Terminated);
					}
				}
				catch  {
				}
			}
		}

		#endregion

		#region method m_pTimerE_Elapsed

		/// <summary>
		/// Is raised when Non-INVITE timer E triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerE_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			/* RFC 3261 17.1.2.2.
                If timer E fires while in Trying state, the timer is reset, but this time with a value of MIN(2*T1, T2).
                When the timer fires again, it is reset to a MIN(4*T1, T2). This process continues so that 
                retransmissions occur with an exponentially increasing interval that caps at T2. The default 
                value of T2 is 4s, and it represents the amount of time a non-INVITE server transaction will take to
                respond to a request, if it does not respond immediately. For the default values of T1 and T2, 
                this results in intervals of 500 ms, 1 s, 2 s, 4 s, 4 s, 4 s, etc.
            */

			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Trying) {
						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(-NonINVITE request retransmission) triggered.");
						}

						try {
							// Retransmit request.
							this.Stack.TransportLayer.SendRequest(this.Flow, this.Request, this);
						}
						catch (Exception x) {
							OnTransportError(x);
							SetState(SIP_TransactionState.Terminated);
							return;
						}

						// Update(double current) next transmit time.
						m_pTimerE.Interval = Math.Min(m_pTimerE.Interval * 2, SIP_TimerConstants.T2);
						m_pTimerE.Enabled = true;

						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) updated, will trigger after " + m_pTimerE.Interval + ".");
						}
					}
				}
				catch  {
				}
			}
		}

		#endregion

		#region method m_pTimerF_Elapsed

		/// <summary>
		/// Is raised when Non-INVITE timer F triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerF_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			/* RFC 3261 17.1.2.2.
                If Timer F fires while in the "Trying" state, the client transaction SHOULD inform the TU about the
                timeout, and then it SHOULD enter the "Terminated" state.
            
                If timer F fires while in the "Proceeding" state, the TU MUST be informed of a timeout, and the
                client transaction MUST transition to the terminated state.
            */

			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Trying || this.State == SIP_TransactionState.Proceeding) {
						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) triggered.");
						}

						OnTimedOut();

						// User Disposed this transaction in TimedOut
						if (this.State == SIP_TransactionState.Disposed) {
							// Do nothing.
						}
						// Switch to terminated state.
						else {
							SetState(SIP_TransactionState.Terminated);

							if (m_pTimerE != null) {
								m_pTimerE.Dispose();
								m_pTimerE = null;
							}
							if (m_pTimerF != null) {
								m_pTimerF.Dispose();
								m_pTimerF = null;
							}
						}
					}
				}
				catch {
				}
			}
		}

		#endregion

		#region method m_pTimerK_Elapsed

		/// <summary>
		/// Is raised when Non-INVITE timer K triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerK_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Completed) {
						// Log
						if (this.Stack.Logger != null) {
							this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) triggered.");
						}

						SetState(SIP_TransactionState.Terminated);
					}
				}
				catch {
				}
			}
		}

		#endregion

		#region method m_pTimerM_Elapsed

		/// <summary>
		/// Is called when INVITE timer M triggered.
		/// </summary>
		/// <param name="sender">Sender.</param>
		/// <param name="e">Event data.</param>
		private void m_pTimerM_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
			/* RFC 6026 7.2.
                When Timer M fires and the state machine is in the "Accepted" state,
                the machine MUST transition to the "Terminated" state.
            */

			lock (this.SyncRoot) {
				// Log
				try {
					if (this.Stack.Logger != null) {
						this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer M(2xx response retransmission wait) triggered.");
					}

					SetState(SIP_TransactionState.Terminated);
				}
				catch {
				}
			}
		}

		#endregion

		#endregion


		#region method Start

		/// <summary>
		/// Starts transaction processing.
		/// </summary>
		/// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
		/// <exception cref="InvalidOperationException">Is raised when <b>Start</b> is called other state than 'WaitingToStart'.</exception>
		public void Start() {
			lock (this.SyncRoot) {
				if (this.State == SIP_TransactionState.Disposed) {
					throw new ObjectDisposedException(this.GetType().Name);
				} else if (this.State != SIP_TransactionState.WaitingToStart) {
					throw new InvalidOperationException("Start method is valid only in 'WaitingToStart' state.");
				}

				// Move processing to thread pool.
				ThreadPool.QueueUserWorkItem(new WaitCallback(delegate (object state) {
					lock (this.SyncRoot) {
						try {
							#region INVITE

							if (this.Method == SIP_Methods.INVITE) {
								/* RFC 3261 17.1.1.2.
									The initial state, "calling", MUST be entered when the TU
									initiates a new client transaction with an INVITE request.  The
									client transaction MUST pass the request to the transport layer for
									transmission (see Section 18).  If an unreliable transport is being
									used, the client transaction MUST start timer A with a value of T1.
									If a reliable transport is being used, the client transaction SHOULD
									NOT start timer A (Timer A controls request retransmissions).  For
									any transport, the client transaction MUST start timer B with a value
									of 64*T1 seconds (Timer B controls transaction timeouts).
								*/

								SetState(SIP_TransactionState.Calling);

								try {
									// Send initial request.
									this.Stack.TransportLayer.SendRequest(this.Flow, this.Request, this);
								}
								catch (Exception x) {
									OnTransportError(x);
									// NOTE: TransportError event handler could Dispose this transaction, so we need to check it.
									if (this.State != SIP_TransactionState.Disposed) {
										SetState(SIP_TransactionState.Terminated);
									}
									return;
								}

								// Start timer A for unreliable transports.
								if (!this.Flow.IsReliable) {
									m_pTimerA = new TimerEx(SIP_TimerConstants.T1, false);
									m_pTimerA.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerA_Elapsed);
									m_pTimerA.Enabled = true;

									// Log
									if (this.Stack.Logger != null) {
										this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) started, will trigger after " + m_pTimerA.Interval + ".");
									}
								}

								// Start timer B.
								m_pTimerB = new TimerEx(64 * SIP_TimerConstants.T1, false);
								m_pTimerB.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerB_Elapsed);
								m_pTimerB.Enabled = true;

								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) started, will trigger after " + m_pTimerB.Interval + ".");
								}
							}

							#endregion

							#region Non-INVITE

						else {
								/* RFC 3261 17.1.2.2.
									The "Trying" state is entered when the TU initiates a new client
									transaction with a request.  When entering this state, the client
									transaction SHOULD set timer F to fire in 64*T1 seconds.  The request
									MUST be passed to the transport layer for transmission.  If an
									unreliable transport is in use, the client transaction MUST set timer
									E to fire in T1 seconds.
								*/

								SetState(SIP_TransactionState.Trying);

								// Start timer F.
								m_pTimerF = new TimerEx(64 * SIP_TimerConstants.T1, false);
								m_pTimerF.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerF_Elapsed);
								m_pTimerF.Enabled = true;

								                                 
								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) started, will trigger after " + m_pTimerF.Interval + ".");
								}


								try {
									// Send initial request.
									this.Stack.TransportLayer.SendRequest(this.Flow, this.Request, this);
								}
								catch (Exception x) {
									OnTransportError(x);
									// NOTE: TransportError event handler could Dispose this transaction, so we need to check it.
									if (this.State != SIP_TransactionState.Disposed) {
										SetState(SIP_TransactionState.Terminated);
									}
									return;
								}

								// Start timer E for unreliable transports.
								if (!this.Flow.IsReliable) {
									m_pTimerE = new TimerEx(SIP_TimerConstants.T1, false);
									m_pTimerE.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerE_Elapsed);
									m_pTimerE.Enabled = true;

									// Log
									if (this.Stack.Logger != null) {
										this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) started, will trigger after " + m_pTimerE.Interval + ".");
									}
								}
							}
						}

						#endregion
						catch {
						}
					}
				}));
			}
		}

		#endregion

		#region method Cancel

		/// <summary>
		/// Starts canceling transaction. 
		/// </summary>
		/// <remarks>If client transaction has got final response, canel has no effect and will be ignored.</remarks>
		/// <exception cref="ObjectDisposedException">Is raised when this class is Disposed and this method is accessed.</exception>
		public override void Cancel() {
			lock (this.SyncRoot) {
				try {
					if (this.State == SIP_TransactionState.Disposed) {
						throw new ObjectDisposedException(this.GetType().Name);
					} else if (this.State == SIP_TransactionState.WaitingToStart) {
						SetState(SIP_TransactionState.Terminated);
						return;
					} else if (m_IsCanceling) {
						return;
					} else if (this.State == SIP_TransactionState.Terminated) {
						// RFC 3261 9.1. We got final response, nothing to cancel.
						return;
					}
					if (this.FinalResponse != null) {
						return;
					}

					m_IsCanceling = true;

					/* RFC 3261 9.1.
						If no provisional response has been received, the CANCEL request MUST
						NOT be sent; rather, the client MUST wait for the arrival of a
						provisional response before sending the request.
					*/
					if (this.Responses.Length == 0) {
						// We set canceling flag, so if provisional response arrives, we do cancel.
					} else {
						SendCancel();
					}
				}
				catch {
				}
			}
		}

		#endregion


		#region method ProcessResponse

		/// <summary>
		/// Processes specified response through this transaction.
		/// </summary>
		/// <param name="flow">SIP data flow what received response.</param>
		/// <param name="response">SIP response to process.</param>
		/// <exception cref="ArgumentNullException">Is raised when <b>flow</b>,<b>response</b> is null reference.</exception>
		internal void ProcessResponse(SIP_Flow flow, SIP_Response response) {
			if (flow == null) {
				throw new ArgumentNullException("flow");
			}
			if (response == null) {
				throw new ArgumentNullException("response");
			}

			lock (this.SyncRoot) {
				try {

					if (this.State == SIP_TransactionState.Disposed) {
						return;
					}
					/* RFC 3261 9.1. CANCEL.
						*) If provisional response, send CANCEL, we should get '478 Request terminated'.
						*) If final response, skip canceling, nothing to cancel.
					*/
					else if (m_IsCanceling && response.StatusCodeType == SIP_StatusCodeType.Provisional) {
						SendCancel();
						return;
					}

					// Log
					if (this.Stack.Logger != null) {
						var responseData = response.ToByteData();

						this.Stack.Logger.AddRead(
							Guid.NewGuid().ToString(),
							null,
							0,
							"Response [transactionID='" + this.ID + "'; method='" + response.CSeq.RequestMethod + "'; cseq='" + response.CSeq.SequenceNumber + "'; " +
							"transport='" + flow.Transport + "'; size='" + responseData.Length + "'; statusCode='" + response.StatusCode + "'; " +
							"reason='" + response.ReasonPhrase + "'; received '" + flow.LocalEP + "' <- '" + flow.RemoteEP + "'.",
							flow.LocalEP,
							flow.RemoteEP,
							responseData
						);
					}


					#region INVITE

					/* RFC 6026 7.2. INVITE client transaction. (Udpates RFC 3261)
						  +-----------+                        +-----------+
						  |           |                        |           |
						  |  Calling  |                        |  Calling  |
						  |           |----------->+           |           |-----------+
						  +-----------+ 2xx        |           +-----------+ 2xx       |
										2xx to TU  |                         2xx to TU |
												   |                                   |
												   |                                   |
												   |                                   |
												   |                                   |
						  +-----------+            |           +-----------+           |
						  |           |            |           |           |           |
						  |Proceeding |----------->|           |Proceeding |---------->|
						  |           | 2xx        |           |           | 2xx       |
						  +-----------+ 2xx to TU  |           +-----------+ 2xx to TU |
												   |                                   |
												   |                                   |
												   |                                   |
												   |                                   V
												   |                            +-----------+
												   |                            |           |
												   |                            | Accepted  |
												   |                        +---|           |
												   |              2xx       |   +-----------+
												   |              2xx to TU |     ^    |
												   |                        |     |    |
												   |                        +-----+    |
												   |                                   |
												   |                 +-----------------+
												   |                 | Timer M fires
												   |                 | -
												   |                 V
						  +-----------+            |           +-----------+
						  |           |            |           |           |
						  | Terminated|<-----------+           | Terminated|
						  |           |                        |           |
						  +-----------+                        +-----------+


					*/

					if (this.Method == SIP_Methods.INVITE) {
						#region Calling

						if (this.State == SIP_TransactionState.Calling) {
							// Store response.
							AddResponse(response);

							// Stop timer A,B
							if (m_pTimerA != null) {
								m_pTimerA.Dispose();
								m_pTimerA = null;

								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer A(INVITE request retransmission) stopped.");
								}
							}
							if (m_pTimerB != null) {
								m_pTimerB.Dispose();
								m_pTimerB = null;

								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer B(INVITE calling state timeout) stopped.");
								}
							}

							// 1xx response.
							if (response.StatusCodeType == SIP_StatusCodeType.Provisional) {
								OnResponseReceived(response);
								SetState(SIP_TransactionState.Proceeding);
							}
							// 2xx response.
							else if (response.StatusCodeType == SIP_StatusCodeType.Success) {
								OnResponseReceived(response);
								SetState(SIP_TransactionState.Accpeted);

								/* RFC 6025 7.1.
									When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1.
								*/
								m_pTimerM = new TimerEx(64 * SIP_TimerConstants.T1);
								m_pTimerM.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerM_Elapsed);
								m_pTimerM.Enabled = true;

								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer M(2xx retransmission wait) started, will trigger after " + m_pTimerM.Interval + ".");
								}
							}
							// 3xx - 6xx response.
							else {
								SendAck(response);
								OnResponseReceived(response);
								SetState(SIP_TransactionState.Completed);

								/* RFC 3261 17.1.1.2. 
									The client transaction SHOULD start timer D when it enters the "Completed" state, 
									with a value of at least 32 seconds for unreliable transports, and a value of zero 
									seconds for reliable transports.
								*/
								m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000, false);
								m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed);
								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + ".");
								}
								m_pTimerD.Enabled = true;
							}
						}

						#endregion

						#region Proceeding

						else if (this.State == SIP_TransactionState.Proceeding) {
							// Store response.
							AddResponse(response);

							// 1xx response.
							if (response.StatusCodeType == SIP_StatusCodeType.Provisional) {
								OnResponseReceived(response);
							}
							// 2xx response.
							else if (response.StatusCodeType == SIP_StatusCodeType.Success) {
								OnResponseReceived(response);
								SetState(SIP_TransactionState.Accpeted);

								/* RFC 6025 7.1.
									When the "Accepted" state is entered, timer L MUST be set to fire in 64*T1.
								*/
								m_pTimerM = new TimerEx(64 * SIP_TimerConstants.T1);
								m_pTimerM.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerM_Elapsed);
								m_pTimerM.Enabled = true;

								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=true] timer M(2xx retransmission wait) started, will trigger after " + m_pTimerM.Interval + ".");
								}
							}
							// 3xx - 6xx response.
							else {
								SendAck(response);
								OnResponseReceived(response);
								SetState(SIP_TransactionState.Completed);

								/* RFC 3261 17.1.1.2. 
									The client transaction SHOULD start timer D when it enters the "Completed" state, 
									with a value of at least 32 seconds for unreliable transports, and a value of zero 
									seconds for reliable transports.
								*/
								m_pTimerD = new TimerEx(this.Flow.IsReliable ? 0 : 32000, false);
								m_pTimerD.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerD_Elapsed);
								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer D(INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerD.Interval + ".");
								}
								m_pTimerD.Enabled = true;
							}
						}

						#endregion

						#region Accepted

						else if (this.State == SIP_TransactionState.Accpeted) {
							if (response.StatusCodeType == SIP_StatusCodeType.Success) {
								OnResponseReceived(response);
							}
						}

						#endregion

						#region Completed

						else if (this.State == SIP_TransactionState.Completed) {
							// 3xx - 6xx
							if (response.StatusCode >= 300) {
								SendAck(response);
							}
						}

						#endregion

						#region Terminated

						else if (this.State == SIP_TransactionState.Terminated) {
							// We should never reach here, but if so, do nothing.
						}

						#endregion
					}

					#endregion

					#region Non-INVITE

					/* RFC 3251 17.1.2.2
												   |Request from TU
												   |send request
							   Timer E             V
							   send request  +-----------+
								   +---------|           |-------------------+
								   |         |  Trying   |  Timer F          |
								   +-------->|           |  or Transport Err.|
											 +-----------+  inform TU        |
								200-699         |  |                         |
								resp. to TU     |  |1xx                      |
								+---------------+  |resp. to TU              |
								|                  |                         |
								|   Timer E        V       Timer F           |
								|   send req +-----------+ or Transport Err. |
								|  +---------|           | inform TU         |
								|  |         |Proceeding |------------------>|
								|  +-------->|           |-----+             |
								|            +-----------+     |1xx          |
								|              |      ^        |resp to TU   |
								| 200-699      |      +--------+             |
								| resp. to TU  |                             |
								|              |                             |
								|              V                             |
								|            +-----------+                   |
								|            |           |                   |
								|            | Completed |                   |
								|            |           |                   |
								|            +-----------+                   |
								|              ^   |                         |
								|              |   | Timer K                 |
								+--------------+   | -                       |
												   |                         |
												   V                         |
							 NOTE:           +-----------+                   |
											 |           |                   |
						 transitions         | Terminated|<------------------+
						 labeled with        |           |
						 the event           +-----------+
						 over the action
						 to take
					*/

					else {
						#region Trying

						if (this.State == SIP_TransactionState.Trying) {
							// Store response.
							AddResponse(response);

							// Stop timer E
							if (m_pTimerE != null) {
								m_pTimerE.Dispose();
								m_pTimerE = null;

								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer E(Non-INVITE request retransmission) stopped.");
								}
							}

							// 1xx response.
							if (response.StatusCodeType == SIP_StatusCodeType.Provisional) {
								OnResponseReceived(response);
								SetState(SIP_TransactionState.Proceeding);
							}
							// 2xx - 6xx response.
							else {
								// Stop timer F
								if (m_pTimerF != null) {
									m_pTimerF.Dispose();
									m_pTimerF = null;

									// Log
									if (this.Stack.Logger != null) {
										this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped.");
									}
								}

								OnResponseReceived(response);
								SetState(SIP_TransactionState.Completed);

								/* RFC 3261 17.1.2.2. 
									The client transaction enters the "Completed" state, it MUST set
									Timer K to fire in T4 seconds for unreliable transports, and zero
									seconds for reliable transports.
								*/
								m_pTimerK = new TimerEx(this.Flow.IsReliable ? 1 : SIP_TimerConstants.T4, false);
								m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed);
								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + ".");
								}
								m_pTimerK.Enabled = true;
							}
						}

						#endregion

						#region Proceeding

						else if (this.State == SIP_TransactionState.Proceeding) {
							// Store response.
							AddResponse(response);

							// 1xx response.
							if (response.StatusCodeType == SIP_StatusCodeType.Provisional) {
								OnResponseReceived(response);
							}
							// 2xx - 6xx response.
							else {
								// Stop timer F
								if (m_pTimerF != null) {
									m_pTimerF.Dispose();
									m_pTimerF = null;

									// Log
									if (this.Stack.Logger != null) {
										this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer F(Non-INVITE trying,proceeding state timeout) stopped.");
									}
								}

								OnResponseReceived(response);
								SetState(SIP_TransactionState.Completed);

								/* RFC 3261 17.1.2.2. 
									The client transaction enters the "Completed" state, it MUST set
									Timer K to fire in T4 seconds for unreliable transports, and zero
									seconds for reliable transports.
								*/
								m_pTimerK = new TimerEx(this.Flow.IsReliable ? 0 : SIP_TimerConstants.T4, false);
								m_pTimerK.Elapsed += new System.Timers.ElapsedEventHandler(m_pTimerK_Elapsed);
								// Log
								if (this.Stack.Logger != null) {
									this.Stack.Logger.AddText(this.ID, "Transaction [branch='" + this.ID + "';method='" + this.Method + "';IsServer=false] timer K(Non-INVITE 3xx - 6xx response retransmission wait) started, will trigger after " + m_pTimerK.Interval + ".");
								}
								m_pTimerK.Enabled = true;
							}
						}

						#endregion

						#region Completed

						else if (this.State == SIP_TransactionState.Completed) {
							// Eat retransmited response.
						}

						#endregion

						#region Terminated

						else if (this.State == SIP_TransactionState.Terminated) {
							// We should never reach here, but if so, do nothing.
						}

						#endregion
					}

					#endregion
				}
				catch {
				}
			}
		}

		#endregion

		#region method SendCancel

		/// <summary>
		/// Creates and send CANCEL request to remote target.
		/// </summary>
		private void SendCancel() {
			/* RFC 3261 9.1.
				The following procedures are used to construct a CANCEL request.  The
				Request-URI, Call-ID, To, the numeric part of CSeq, and From header
				fields in the CANCEL request MUST be identical to those in the
				request being cancelled, including tags.  A CANCEL constructed by a
				client MUST have only a single Via header field value matching the
				top Via value in the request being cancelled.  Using the same values
				for these header fields allows the CANCEL to be matched with the
				request it cancels (Section 9.2 indicates how such matching occurs).
				However, the method part of the CSeq header field MUST have a value
				of CANCEL.  This allows it to be identified and processed as a
				transaction in its own right (See Section 17).

				If the request being cancelled contains a Route header field, the
				CANCEL request MUST include that Route header field's values.

					This is needed so that stateless proxies are able to route CANCEL
					requests properly.
			*/
			try {
				var cancelRequest = new SIP_Request(SIP_Methods.CANCEL);
				cancelRequest.RequestLine.Uri = this.Request.RequestLine.Uri;
				cancelRequest.Via.Add(this.Request.Via.GetTopMostValue().ToStringValue());
				cancelRequest.CallID = this.Request.CallID;
				cancelRequest.From = this.Request.From;
				cancelRequest.To = this.Request.To;
				cancelRequest.CSeq = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber, SIP_Methods.CANCEL);
				foreach (var route in this.Request.Route.GetAllValues()) {
					cancelRequest.Route.Add(route.ToStringValue());
				}
				cancelRequest.MaxForwards = 70;

				// We must use same data flow to send CANCEL what sent initial request.
				var transaction = this.Stack.TransactionLayer.CreateClientTransaction(this.Flow, cancelRequest, false);
				transaction.Start();
			}
			catch {
			}
		}

		#endregion

		#region method SendAck

		/// <summary>
		/// Creates and sends ACK for final(3xx - 6xx) failure response.
		/// </summary>
		/// <param name="response">SIP response.</param>
		/// <exception cref="ArgumentNullException">Is raised when <b>response</b> is null.</exception>
		private void SendAck(SIP_Response response) {
			if (response == null) {
				throw new ArgumentNullException("resposne");
			}

			/* RFC 3261 17.1.1.3 Construction of the ACK Request.
				The ACK request constructed by the client transaction MUST contain
				values for the Call-ID, From, and Request-URI that are equal to the
				values of those header fields in the request passed to the transport
				by the client transaction (call this the "original request").  The To
				header field in the ACK MUST equal the To header field in the
				response being acknowledged, and therefore will usually differ from
				the To header field in the original request by the addition of the
				tag parameter.  The ACK MUST contain a single Via header field, and
				this MUST be equal to the top Via header field of the original
				request.  The CSeq header field in the ACK MUST contain the same
				value for the sequence number as was present in the original request,
				but the method parameter MUST be equal to "ACK".

				If the INVITE request whose response is being acknowledged had Route
				header fields, those header fields MUST appear in the ACK.  This is
				to ensure that the ACK can be routed properly through any downstream
				stateless proxies.
			*/
			try {
				var ackRequest = new SIP_Request(SIP_Methods.ACK);
				ackRequest.RequestLine.Uri = this.Request.RequestLine.Uri;
				ackRequest.Via.AddToTop(this.Request.Via.GetTopMostValue().ToStringValue());
				ackRequest.CallID = this.Request.CallID;
				ackRequest.From = this.Request.From;
				ackRequest.To = response.To;
				ackRequest.CSeq = new SIP_t_CSeq(this.Request.CSeq.SequenceNumber, "ACK");
				foreach (var h in response.Header.Get("Route:")) {
					ackRequest.Header.Add("Route:", h.Value);
				}
				ackRequest.MaxForwards = 70;

				try {
					// Send request to target.
					this.Stack.TransportLayer.SendRequest(this.Flow, ackRequest, this);
				}
				catch (SIP_TransportException x) {
					OnTransportError(x);
					SetState(SIP_TransactionState.Terminated);
				}
			}
			catch {
			}
		}

		#endregion


		#region Properties implementation

		// FIX ME:
		int m_RSeq = -1;

		/// <summary>
		/// Gets or sets RSeq value. Value -1 means no reliable provisional response received.
		/// </summary>
		internal int RSeq {
			get { return m_RSeq; }

			set { m_RSeq = value; }
		}

		#endregion

		#region Events implementation

		/// <summary>
		/// Is raised when transaction received response from remote party.
		/// </summary>
		public event EventHandler<SIP_ResponseReceivedEventArgs> ResponseReceived = null;

		#region method OnResponseReceived

		/// <summary>
		/// Raises ResponseReceived event.
		/// </summary>
		/// <param name="response">SIP response received.</param>
		private void OnResponseReceived(SIP_Response response) {
			if (this.ResponseReceived != null) {
				this.ResponseReceived(this, new SIP_ResponseReceivedEventArgs(this.Stack, this, response));
			}
		}

		#endregion

		#endregion

	}
}
